﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Serialization;
using System.Xml.Schema;
using System.Xml;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using TableParsers;
using System.Collections.Observable;
using System.Collections.Specialized;

namespace SpeakupBackend.Themes
{
    [Serializable]
    [XmlRoot("WorkshopThemeCollection")]
    public class WorkshopThemeCollection: ObservableCollectionBase<WorkshopTheme, HashSet<WorkshopTheme> >, IXmlSerializable
    {
        public WorkshopThemeCollection(): base(new HashSet<WorkshopTheme>())
        {
        }

        /// <summary>
        /// Can be used only for just loaded collections.
        /// I.e. no event subscribers are present.
        /// </summary>
        /// <param name="other"></param>
        /// <param name="useTheirsOnConflict"></param>
        void Merge(WorkshopThemeCollection other, bool useTheirsOnConflict)
        {
            using (var transaction = BeginTransaction())
            {
                foreach(var theirsItem in other.InternalCollection)
                {
                    if (InternalCollection.Add(theirsItem))
                    {
                        OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, theirsItem));
                    }
                    else if (useTheirsOnConflict) 
                    {
                        var conflictingTheme = (from th in InternalCollection
                                                where th == theirsItem
                                                select th).First();

                        
                        conflictingTheme.Name = theirsItem.Name;                        
                    }
                }
            }            
        }

        #region Filtering

        public delegate bool ThemeFilter(WorkshopClassType classType, LevelGroup levelGroup, Week week);

        public static ThemeFilter LevelFilter(int level)
        {
            return (ct, lvl, w) =>
            {
                return lvl.MinLevel <= level && level <= lvl.MaxLevel;
            };
        }

        public static ThemeFilter LevelFilter(Level level)
        {
            return (ct, lvl, w) =>
            {
                return lvl.ContainsLevel(level);
            };
        }

        public static ThemeFilter LevelFilter(LevelGroup level)
        {
            return (ct, lvl, w) =>
            {
                return lvl == level;
            };
        }

        public static ThemeFilter WeekFilter(int week)
        {
            return (ct, lvl, w) =>
            {
                return w.WeekNumber == week;
            };
        }

        public static ThemeFilter WeekFilter(Week week)
        {
            return (ct, lvl, w) =>
            {
                return w == week;
            };
        }

        public static ThemeFilter ClassTypeFilter(ClassType classType)
        {
            return (ct, lvl, w) =>
            {
                return ct.ClassType == classType;
            };
        }

        public static ThemeFilter ClassTypeFilter(WorkshopClassType classType)
        {
            return (ct, lvl, w) =>
            {
                return ct == classType;
            };
        }
        
        public static ThemeFilter ClassTypeAndWeekFilter(WorkshopClassType classType, Week week)
        {
            return (ct, lvl, w) =>
            {
                return ct == classType && w == week;
            };
        }

        public static ThemeFilter ClassTypeAndLevelFilter(WorkshopClassType classType, Level level)
        {
            return (ct, lvl, w) =>
            {
                return ct == classType && lvl.ContainsLevel(level);
            };
        }

        public static ThemeFilter ClassTypeAndLevelFilter(WorkshopClassType classType, LevelGroup level)
        {
            return (ct, lvl, w) =>
            {
                return ct == classType &&
                       lvl == level;
            };
        }

        public static ThemeFilter WeekAndLevelFilter(Week week, Level level)
        {
            return (ct, lvl, w) =>
            {
                return w == week && lvl.ContainsLevel(level);
            };
        }

        public static ThemeFilter WeekAndLevelFilter(Week week, LevelGroup level)
        {
            return (ct, lvl, w) =>
            {
                return w == week && lvl == level;
            };
        }

        public static ThemeFilter ByAllFilter(ClassType classType, int level, int week)
        {
            return (ct, lvl, w) =>
            {
                return  ct.ClassType == classType && 
                        lvl.MinLevel <= level &&  level <= lvl.MaxLevel &&
                        w.WeekNumber == week;
            };
        }

        public static ThemeFilter ByAllFilter(WorkshopClassType classType, LevelGroup level, Week week)
        {
            return (ct, lvl, w) =>
            {
                return lvl == level && ct == classType && w == week;
            };
        }

        public WorkshopThemeCollection ApplyFilter(ThemeFilter filter)
        {
            var result = from n in this
                                 where filter(n.ClassType, n.LevelGroup, n.Week)
                                 select n;

            WorkshopThemeCollection set = new WorkshopThemeCollection();
            foreach (WorkshopTheme theme in result)
            {
                set.Add(theme);
            }

            return set;
        }

        #endregion

        #region IXmlSerializable implementation

        private static readonly string ThemeElementName = "WorkshopTheme";

        public XmlSchema GetSchema()
        {
            throw new NotImplementedException();
        }
        
        public void ReadXml(XmlReader reader)
        {
            reader.ReadStartElement();

            while (reader.IsStartElement())
            {
                if (!reader.IsStartElement(ThemeElementName))
                {
                    throw new XmlException(String.Format("Node name {0} is not recognized.", reader.LocalName));
                }

                WorkshopTheme theme = new WorkshopTheme();
                theme.ReadXml(reader);

                if (Contains(theme))
                {
                    Remove(theme);
                }                
                Add(theme);
            }
        }

        public void WriteXml(XmlWriter writer)
        {
            var sortedList = from n in this
                             orderby n.ClassType, n.LevelGroup ascending, n.Week ascending
                             select n;
            
            foreach(WorkshopTheme th in sortedList)
            {
                writer.WriteStartElement(ThemeElementName);
                th.WriteXml(writer);
                writer.WriteEndElement();
            }
        }
        
        #endregion

        #region Xml & Binary persistency

        public void SaveXml(String fileName)
        {
            XmlSerializer serializer = new XmlSerializer(typeof(WorkshopThemeCollection));

            using (TextWriter textWriter = new StreamWriter(fileName))
            {
                serializer.Serialize(textWriter, this);
            }
        }

        public static WorkshopThemeCollection LoadXml(String fileName)
        {
            XmlSerializer serializer = new XmlSerializer(typeof(WorkshopThemeCollection));

            using (TextReader textReader = new StreamReader(fileName))
            {
                return (WorkshopThemeCollection)serializer.Deserialize(textReader);
            }
        }

        /*public void MergeFromXml(String fileName, bool useTheirsOnConflict)
        {
            WorkshopThemeCollection otherCollection = LoadXml(fileName);
            Merge(otherCollection, useTheirsOnConflict);
        }*/

        public void ReloadFromXml(String fileName)
        {
            Clear();

            WorkshopThemeCollection otherCollection = LoadXml(fileName);
            Merge(otherCollection, true);
        }

        public void Save(String fileName)
        {
            BinaryFormatter formatter = new BinaryFormatter();
            using (FileStream fileStream = new FileStream(fileName, FileMode.Create))
            {
                formatter.Serialize(fileStream, this);
            }
        }

        public static WorkshopThemeCollection Load(String fileName)
        {
            BinaryFormatter formatter = new BinaryFormatter();
            using (FileStream fileStream = new FileStream(fileName, FileMode.Open))
            {
                return (WorkshopThemeCollection)formatter.Deserialize(fileStream);
            }
        }

        public void ReloadFrom(String fileName)
        {
            Clear();

            WorkshopThemeCollection otherCollection = Load(fileName);
            Merge(otherCollection, true);            
        }

        #endregion
    }

}
